"""
各种路由的具体实现
https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html  aws官方文档
https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList 文档中错误码列表
"""

import yarl
import shutil
import hashlib
import datetime
import aiofiles

from pathlib import Path
from loguru import logger
from fastapi.responses import Response, RedirectResponse
from fastapi import Request, HTTPException, status, APIRouter

from data.global_var import STORAGE_DIR, REGION
from routers.xml_temp import policy_xml, generate_list_objects_response
from tools import generate_formatted_xml, normalize_object_name
from tools.signer import verify_signature

action_router = APIRouter()  # 具体执行动作的路由

STORAGE_DIR = Path(STORAGE_DIR)


# 创建桶
@action_router.put("/{bucket}")
async def make_bucket(bucket: str, request: Request):
    """
    创建桶
    :param bucket: 桶名
    :param request: 请求的 Request 对象
    :return: Response 对象
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", bucket),
            media_type="text/xml",
            status_code=404
        )
    # 创建桶目录
    bucket_dir = STORAGE_DIR / bucket
    bucket_dir.mkdir(parents=True, exist_ok=True)

    return Response(status_code=status.HTTP_200_OK)


# 列出所有可访问的桶
@action_router.get("/")
async def list_buckets(request: Request):
    """
    列出所有可访问的桶。
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", '/'),
            media_type="text/xml",
            status_code=404
        )

    # 获取所有桶
    buckets = [d.name for d in Path(STORAGE_DIR).iterdir() if d.is_dir()]
    # 构建响应
    content = f"""<?xml version="1.0" encoding="UTF-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Owner>
    <ID>fe7272ea58be830e56fe1663b10fafef</ID>
    <DisplayName>GoFakeS3</DisplayName>
  </Owner>
  <Buckets>{''.join([f'''
        <Bucket>
            <Name>{bucket}</Name>
            <CreationDate>2024-01-01T00:00:00.000Z</CreationDate>
        </Bucket>''' for bucket in buckets])}
  </Buckets>
</ListAllMyBucketsResult>"""
    return Response(
        content=content,
        media_type="application/xml"
    )


# 获取桶信息(可以用作检测桶是否存在)
@action_router.head("/{bucket}")
async def bucket_exists(request: Request, bucket: str):
    """
    获取桶信息(可以用作检测桶是否存在)
    :param request: 请求的 Request 对象
    :param bucket: 桶名
    :return 如果桶存在，则返回桶的信息，否则返回 404
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", bucket),
            media_type="text/xml",
            status_code=404
        )
    # 如果桶不存在，则返回 404
    if not ((STORAGE_DIR / bucket).exists()):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="桶不存在")
    # 返回桶的信息
    return Response(
        content=f"""
        <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
            <Buckets>
                <Bucket>
                    <Name>{bucket}</Name>
                    <CreationDate>2024-01-01T00:00:00.000Z</CreationDate>
                </Bucket>
            </Buckets>
        </ListAllMyBucketsResult>
        """,
        media_type="application/xml"
    )


# 删除一个桶
@action_router.delete("/{bucket}")
async def remove_bucket(bucket: str, request: Request):
    """
    删除一个桶。
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", bucket),
            media_type="text/xml",
            status_code=404
        )

    # 检查桶是否存在
    bucket_dir = Path(STORAGE_DIR) / bucket
    if not bucket_dir.exists():
        logger.error(f"删除桶失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )
    # 删除桶
    try:
        shutil.rmtree(bucket_dir)
    except Exception as e:
        logger.error(f"删除桶失败:{bucket} \n{str(e)}")
        return Response(
            content=generate_formatted_xml("DeleteBucketFailed", f"Failed to delete bucket \n{str(e)}", bucket),
            media_type="text/xml",
            status_code=500
        )

    return Response(status_code=status.HTTP_204_NO_CONTENT)


# 删除一个对象
@action_router.delete("/{bucket}/{object_key:path}")
async def remove_object(bucket: str, object_key: str, request: Request):
    """
    删除一个对象。
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", f"{bucket}/{object_key}"),
            media_type="text/xml",
            status_code=404
        )

    # 检查桶是否存在
    bucket_dir = Path(STORAGE_DIR) / bucket
    if not bucket_dir.exists():
        logger.error(f"删除对象失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )
    # 规范化 object_key
    object_key = normalize_object_name(bucket, object_key)
    # 删除对象
    file_path = bucket_dir / object_key
    if not file_path.exists():
        logger.error(f"删除对象失败:对象{object_key}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchKey", "The specified key does not exist", f"{bucket}/{object_key}"),
            media_type="text/xml",
            status_code=404
        )
    try:
        file_path.unlink()
    except Exception as e:
        logger.error(f"删除对象失败:{object_key} \n{str(e)}")
        return Response(
            content=generate_formatted_xml("DeleteObjectFailed", f"Failed to delete object \n{str(e)}",
                                           f"{bucket}/{object_key}"),
            media_type="text/xml",
            status_code=500
        )

    return Response(status_code=status.HTTP_200_OK)


# 上传文件  # 这里的 :path 表示 object_key 参数可以包含 / 字符。这意味着您可以传递像 mybucket/abc.png 这样的路径作为 object_key 参数。
@action_router.put("/{bucket}/{object_key:path}")
async def put_object(request: Request, bucket: str, object_key: str):
    """
    上传文件
    :param request: 请求的 Request 对象
    :param bucket:  桶名
    :param object_key: 文件key 可以使用文件名
    :return: Response 对象
    """
    content = await request.body()
    # 验证签名
    if not await verify_signature(request, content):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", f"{bucket}/{object_key}"),
            media_type="text/xml",
            status_code=404
        )
    # 规范化 object_key
    object_key = normalize_object_name(bucket, object_key)
    # 目标桶目录
    bucket_dir = STORAGE_DIR / bucket
    if not bucket_dir.exists():  # 目标桶不存在
        logger.error(f"上传失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )
    content_length = int(request.headers.get("Content-Length"))
    copy_source = request.headers.get('x-amz-copy-source')
    if copy_source:
        copy_source = copy_source.strip("/")
    # 复制文件
    if content_length == 0 and copy_source:
        # 检测copy_source是否存在
        source_bucket, source_object_key = copy_source.split('/', 1)
        source_object_key = normalize_object_name(source_bucket, source_object_key)
        source_path = STORAGE_DIR / source_bucket / source_object_key
        if not source_path.exists():  # 源文件不存在
            # 判断桶是否存在
            if not (STORAGE_DIR / bucket).exists():
                logger.error(f"上传失败:源桶{source_bucket}不存在")
                return Response(
                    content=generate_formatted_xml("NoSuchBucket", "The source bucket does not exist", bucket),
                    media_type="text/xml",
                    status_code=404
                )
            logger.error(f"上传失败:源文件{source_object_key}不存在")
            return Response(
                content=generate_formatted_xml("NoSuchKey", "The source object does not exist",
                                               f"{source_bucket}/{source_object_key}"),
                media_type="text/xml",
                status_code=404
            )
        print(f'复制文件:{source_path} -> {bucket_dir / object_key}')
        # 复制文件
        try:
            if not (bucket_dir / object_key).parent.exists():
                (bucket_dir / object_key).parent.mkdir(parents=True, exist_ok=True)
            shutil.copy(source_path, bucket_dir / object_key)
            file_path = source_path
        except Exception as e:
            logger.error(f"上传失败:{bucket}/{object_key} \n{str(e)}")
            return Response(
                content=generate_formatted_xml("CopyObjectFailed", f"Failed to copy object \n{str(e)}",
                                               f"{bucket}/{object_key}"),
                media_type="text/xml",
                status_code=500
            )
    else:  # 上传文件
        # 保存文件
        file_path = bucket_dir / object_key
        print(f'上传文件到:{file_path}')
        if not file_path.parent.exists():
            file_path.parent.mkdir(parents=True, exist_ok=True)
        try:
            async with aiofiles.open(file_path, "wb") as f:
                await f.write(content)
        except Exception as e:
            logger.error(f"上传失败:{bucket}/{object_key} \n{str(e)}")
            return Response(
                content=generate_formatted_xml("UploadObjectFailed", f"Failed to upload object \n{str(e)}",
                                               f"{bucket}/{object_key}"),
                media_type="text/xml",
                status_code=500
            )
    # 获取文件元数据
    file_size = file_path.stat().st_size
    last_modified = datetime.datetime.fromtimestamp(file_path.stat().st_mtime).strftime("%Y-%m-%dT%H:%M:%SZ")
    content_type = "application/octet-stream"
    # 异步计算md5值
    async with aiofiles.open(file_path, "rb") as f:
        md5_hash = hashlib.md5()
        chunk = await f.read(1024)
        while chunk:
            md5_hash.update(chunk)
            chunk = await f.read(1024)
        etag = md5_hash.hexdigest()
    data = f"""
    <CopyObjectResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
        <ETag>"{etag}"</ETag>
        <LastModified>{last_modified}</LastModified>
        <FileSize>{file_size}</FileSize>
    </CopyObjectResult>
    """
    headers = {
        "ETag": f'"{etag}"',
        "Last-Modified": last_modified,
        "Content-Type": content_type,
        "Content-Length": str(len(data)),
        "Server": "MinIO/RELEASE.2021-09-18T23-19-30Z"
    }
    return Response(data, headers=headers)


# 获取桶的位置region信息
@action_router.get("/{bucket}")
@action_router.get("/{bucket}/")
async def get_bucket_region(request: Request, bucket: str):
    """
    获取桶的位置region信息
    :param bucket: 桶名
    :param request: 请求的 Request 对象
    :return: Response 对象
    """
    # 获取request url 的fragments
    query_str = request.url.query
    # 是否是获取图标请求
    if query_str == '' and bucket == 'favicon.ico':
        return RedirectResponse(url="/static/python.png")
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", bucket),
            media_type="text/xml",
            status_code=404
        )
    if query_str == 'location=' or query_str == 'location':  # 获取region信息
        # 返回桶的位置信息
        return Response(
            content=f"""
            <LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">{REGION}</LocationConstraint>
            """,
            media_type="application/xml"
        )
    elif query_str == 'policy=' or query_str == 'policy':  # 获取策略信息
        # 检测桶是否存在
        bucket_dir = Path(STORAGE_DIR) / bucket
        if not bucket_dir.exists():
            logger.error(f"获取策略失败:桶{bucket}不存在")
            return Response(
                content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
                media_type="text/xml",
                status_code=404
            )
        # 构建策略信息
        data = policy_xml(bucket)
        headers = {
            "Content-Type": "application/xml",
            "Content-Length": str(len(data)),
            "Server": "MinIO/RELEASE.2021-09-18T23-19-30Z"
        }
        return Response(content=data, headers=headers)
    elif query_str == 'object-lock=' or query_str == 'object-lock':  # 获取object-lock信息
        return Response(
            content=f"""
            <ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
            """,
            media_type="application/xml"
        )
    elif query_str == 'versioning=' or query_str == 'versioning':  # 获取版本控制信息
        return Response(
            content=f"""
            <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                <Status>Enabled</Status>
            </VersioningConfiguration>
            """,
            media_type="application/xml"
        )
    else:
        yarl_url = yarl.URL(str(request.url))
        if all(i in yarl_url.query for i in ['delimiter', 'max-keys', 'prefix']):
            # 列出对象
            return await list_objects(bucket, yarl_url.query['delimiter'],
                                      yarl_url.query['encoding-type'] if 'encoding-type' in yarl_url.query else 'url',
                                      yarl_url.query['list-type'] if 'list-type' in yarl_url.query else 'max-keys',
                                      int(yarl_url.query['max-keys']),
                                      yarl_url.query['prefix'])
        logger.error(f'未知的查询参数:{query_str}')
        # 报错
        return Response(
            content=generate_formatted_xml("InvalidArgument", "Invalid argument", query_str),
            media_type="text/xml",
            status_code=400
        )


# 获取对象的内容  这里的 :path 表示 object_key 参数可以包含 / 字符。这意味着您可以传递像 mybucket/abc.png 这样的路径作为 object_key 参数。
@action_router.get("/{bucket}/{object_key:path}")
async def get_object(request: Request, bucket: str, object_key: str):
    """
    获取对象的内容。
    :param request: 请求的 Request 对象
    :param bucket: 桶名
    :param object_key: 文件key bucket/object_key 或者object_key
    :return: Response 对象
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", f"{bucket}/{object_key}"),
            media_type="text/xml",
            status_code=404
        )
    # 规范化 object_key
    object_key = normalize_object_name(bucket, object_key)
    # 检查桶是否存在
    bucket_dir = Path(STORAGE_DIR) / bucket
    if not bucket_dir.exists():
        logger.error(f"下载失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )

    # 检查对象是否存在
    file_path = STORAGE_DIR / object_key
    if not file_path.exists():
        file_path = bucket_dir / object_key
        if not file_path.exists():
            logger.error(f"下载失败:对象{object_key}不存在")
            return Response(
                content=generate_formatted_xml("NoSuchKey", "The specified key does not exist", object_key),
                media_type="text/xml",
                status_code=404
            )
    # 返回对象内容
    try:
        async with aiofiles.open(file_path, "rb") as f:
            content = await f.read()
        return Response(content=content, media_type="application/octet-stream")
    except Exception as e:
        logger.error(f"下载失败:{object_key} \n{str(e)}")
        return Response(
            content=generate_formatted_xml("DownloadObjectFailed", f"Failed to download key \n {str(e)}", object_key),
            media_type="text/xml",
            status_code=404
        )


# 获取对象的元数据
@action_router.head("/{bucket}/{object_key:path}")
async def stat_object(request: Request, bucket: str, object_key: str):
    """
    获取对象的元数据。
    :param request: 请求的 Request 对象
    :param bucket: 桶名
    :param object_key: 文件key
    :return: Response 对象
    """
    # 验证签名
    if not await verify_signature(request):
        return Response(
            content=generate_formatted_xml("AccessDenied", "Access Signature is not valid", f"{bucket}/{object_key}"),
            media_type="text/xml",
            status_code=404
        )
    # 规范化 object_key
    object_key = normalize_object_name(bucket, object_key)
    # 检查桶是否存在
    bucket_dir = Path(STORAGE_DIR) / bucket
    if not bucket_dir.exists():
        logger.error(f"获取对象元数据失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )

    # 构建文件路径
    file_path = STORAGE_DIR / object_key
    if not file_path.exists():
        file_path = bucket_dir / object_key
        if not file_path.exists():
            logger.error(f"获取对象元数据失败:对象{object_key}不存在")
            return Response(
                content=generate_formatted_xml("NoSuchKey", "The specified key does not exist", object_key),
                media_type="text/xml",
                status_code=404
            )

    # 获取文件元数据
    file_size = file_path.stat().st_size
    last_modified = datetime.datetime.fromtimestamp(file_path.stat().st_mtime).strftime("%a, %d %b %Y %H:%M:%S GMT")
    content_type = "application/octet-stream"
    # 异步计算md5值
    async with aiofiles.open(file_path, "rb") as f:
        md5_hash = hashlib.md5()
        chunk = await f.read(1024)
        while chunk:
            md5_hash.update(chunk)
            chunk = await f.read(1024)
        etag = md5_hash.hexdigest()

    # 构建响应头
    headers = {
        "Content-Length": str(file_size),
        "Content-Type": content_type,
        "Last-Modified": last_modified,
        "ETag": f'"{etag}"'
    }

    return Response(
        headers=headers,
        status_code=status.HTTP_200_OK
    )


async def list_objects_back(bucket: str, delimiter: str, encoding_type: str, marker: str, max_keys: int, prefix: str):
    """
    列出指定桶中的对象。
    :param request: 请求的 Request 对象
    :param bucket: 桶名
    :param delimiter: 分隔符
    :param encoding_type: 编码类型
    :param marker: 标记
    :param max_keys: 最大键数
    :param prefix: 前缀
    :return: Response 对象
    """
    # 检查桶是否存在
    bucket_dir = STORAGE_DIR / bucket
    if not bucket_dir.exists():
        logger.error(f"列出对象失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )
    # 获取桶中的对象列表
    objects = [obj.name for obj in bucket_dir.iterdir() if obj.is_file() and obj.name.startswith(prefix or "")]
    # 如果指定了分隔符，处理公共前缀
    common_prefixes = []
    if delimiter:
        grouped_objects = {}
        for obj in objects:
            parts = obj.split(delimiter)
            prefix = parts[0] + delimiter
            if prefix not in grouped_objects:
                grouped_objects[prefix] = []
            grouped_objects[prefix].append(obj)
        common_prefixes = list(grouped_objects.keys())
        objects = [obj for sublist in grouped_objects.values() for obj in sublist]
    # 应用最大键数限制
    objects = objects[:max_keys]
    # 判断是否截断
    is_truncated = len(objects) == max_keys
    # 生成响应内容
    content = generate_list_objects_response(bucket, objects, prefix or "", delimiter or "", max_keys, is_truncated)
    return Response(
        content=content,
        media_type="text/xml"
    )


async def list_objects(bucket: str, delimiter: str, encoding_type: str, marker: str, max_keys: int, prefix: str):
    """
    列出指定桶中的对象。
    :param bucket: 桶名
    :param delimiter: 分隔符
    :param encoding_type: 编码类型
    :param marker: 标记
    :param max_keys: 最大键数
    :param prefix: 前缀
    :return: Response 对象
    """
    # 检查桶是否存在
    bucket_dir = STORAGE_DIR / bucket
    if not bucket_dir.exists():
        logger.error(f"列出对象失败:桶{bucket}不存在")
        return Response(
            content=generate_formatted_xml("NoSuchBucket", "The specified bucket does not exist", bucket),
            media_type="text/xml",
            status_code=404
        )
    # 获取桶中的对象列表，递归查找
    if prefix:
        search_pattern = f"{prefix}*"
    else:
        search_pattern = "*"
    objects = [str(obj.relative_to(bucket_dir)) for obj in bucket_dir.rglob(search_pattern) if obj.is_file()]

    # 按文件夹层级分组并排序
    dir_groups = {}
    for obj in objects:
        dir_path = str(Path(obj).parent)
        if dir_path not in dir_groups:
            dir_groups[dir_path] = []
        dir_groups[dir_path].append(obj)

    sorted_objects = []

    def get_mtime(obj):
        obj_path = Path(STORAGE_DIR) / bucket / obj
        return obj_path.stat().st_mtime

    for dir_path in sorted(dir_groups.keys(), key=lambda x: x.count('/')):
        group = dir_groups[dir_path]
        sorted_group = sorted(group, key=get_mtime)
        sorted_objects.extend(sorted_group)

    # 如果指定了分隔符，处理公共前缀
    common_prefixes = []
    if delimiter:
        prefix_set = set()
        for obj in sorted_objects:
            parts = obj.split(delimiter)
            for i in range(1, len(parts)):
                current_prefix = "/".join(parts[:i]) + "/"
                prefix_set.add(current_prefix)
        common_prefixes = sorted(list(prefix_set))
        # 过滤掉已经在 common_prefixes 中的对象
        sorted_objects = [obj for obj in sorted_objects if
                          not any(obj.startswith(prefix) and len(obj) > len(prefix) for prefix in common_prefixes)]
    # 应用最大键数限制
    sorted_objects = sorted_objects[:max_keys]
    # 判断是否截断
    is_truncated = len(sorted_objects) == max_keys
    # 生成响应内容
    content = generate_list_objects_response(bucket, sorted_objects, prefix or "", delimiter or "", max_keys,
                                             is_truncated, common_prefixes)
    headers = {
        'Access-Control-Allow-Headers': 'Accept, Accept-Encoding, Authorization, Content-Disposition, Content-Length, Content-Type, X-Amz-Date, X-Amz-User-Agent, X-CSRF-Token, x-amz-acl, x-amz-content-sha256, x-amz-meta-filename, x-amz-meta-from, x-amz-meta-private, x-amz-meta-to, x-amz-security-token',
        'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, HEAD', 'Access-Control-Allow-Origin': '*',
        'Access-Control-Expose-Headers': 'ETag', 'Server': 'AmazonS3',
        'X-Amz-Id-2': 'N0VDMUNGM0RENzlBREQwQzdFQzFDRjNERDc5QUREMEM3RUMxQ0YzREQ3OUFERDBDN0VDMUNGM0RENzlBREQwQw==',
        'X-Amz-Request-Id': '7EC1CF3DD79ADD0C',
        'Date': 'Tue, 15 Apr 2025 07:26:11 GMT',

        'Content-Type': 'text/xml; charset=utf-8'}
    return Response(
        content=content,
        media_type="text/xml",
        headers=headers
    )
